/**
 * 
 */
package org.justframework.plugin.http;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Set;

import org.justframework.api.connector.BaseClient;
import org.justframework.api.connector.Request;
import org.justframework.api.connector.Response;
import org.justframework.api.rest.RestMethod;
import org.justframework.core.mapper.MapperFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Http client connector using json package request info
 */
public class HttpClient extends BaseClient{
	/**
	 * logger
	 */
	private static Logger logger = LoggerFactory.getLogger(HttpClient.class);
	
	/**
	 * just framework internal param prefix
	 */
	private static final String JUST_PARAM_PREFIX = "_jt_";
	
	
	public Response doRpc(Request request) {
		String sUrl = request.getUri().toString();
		Map<String, Object> params = request.getParams();
		Map<String, Object> headers = request.getHeaders();
		String encoding = "utf-8";
		if(request.getOptions().containsKey("encoding")){
			encoding = (String)request.getOptions().get("encoding");
		}
		long connectTimeout = request.getConnectTimeout();
		long operateTimeout = request.getOperateTimeout();
		String method = request.getRequestMethod();
		
		String paramStr = JUST_PARAM_PREFIX+"request=" + MapperFactory.getDefaultMapper().toSerial(request);
		//only GET method use url append
		if(RestMethod.GET.equalsIgnoreCase(method)){
			sUrl += "?"+paramStr;
		}
		
		URL url = null;
		HttpURLConnection conn = null;
		try {
			//connect
			url = new URL(sUrl);
			conn = (HttpURLConnection) url.openConnection();
			conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
			
			//according to semantic request method set HTTP communicate method
			//for "PUT" and "DELETE" supported not to good in JDK and server, some issues will happened like follow:
			// 1)send POST method output stream support 
			// 2)send DELETE method output stream not support 
			// 3)send GET method if submit params with output stream, 
			// then in server side method automated changed to POST, so we can't use output stream with GET method
			// 4)send PUT method output stream support but in server side
			// can't get param with param name,so we don't use output stream with PUT method too  			
			String httpMethod = RestMethod.GET.equalsIgnoreCase(method)?"GET":"POST";
			conn.setRequestMethod(httpMethod);
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setConnectTimeout((int)connectTimeout);
			conn.setReadTimeout((int)operateTimeout);
			//specify headers
			if(headers != null && headers.size() > 0){
				Set<String> headerSet = headers.keySet();
				for(String key:headerSet){
					conn.setRequestProperty(key, String.valueOf(headers.get(key)));
				}
			}
			//if not GET method, use OutputStream write params to server side
			if(!RestMethod.GET.equalsIgnoreCase(method)){
				//send request params
				BufferedWriter out = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(),encoding));
				out.write(paramStr);
				out.flush();
				out.close();
			} 
			
			//receive result
			StringBuilder result = new StringBuilder();
			BufferedReader in = new BufferedReader(new InputStreamReader(conn
					.getInputStream(),encoding));
			if(in != null){
				String line = "";
				while ((line = in.readLine()) != null) {
					result.append(line);
				}
				in.close();
			}
			String rtn = result.toString();
			logger.debug("response string:{}",rtn);
			
			//convert json to response bean
			Response response = MapperFactory.getDefaultMapper().toBean(result.toString(), Response.class);
			return response;
		} catch (Exception e) {
			logger.error("http rpc fail! URL:"+url+", params："+params,e);
			//deal with error stream, improved http connection reusing rate
			try {
				byte[] buf = new byte[100];
				InputStream es = conn.getErrorStream();
				if(es != null){
					while (es.read(buf) > 0) {;}
					es.close();
				}
			} catch (Exception e1) {
				e1.printStackTrace();
			}
			throw new RuntimeException(e);
		} finally {
			//close connection
			if (conn != null){
				conn.disconnect();
			}	
		}
	}

}
